﻿using RockBus.DataContracts;
using RockBus.Logging.Interfaces;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.ServiceModel;
using System.Web.Hosting;

namespace RockBus.Hosting.Web
{
    // This is the code that injects the service activation configuration.
    // In WCF 4 we have the "not very well known" CBA Services (Configuration Based Activation)
    // to allow us to define "file-less" WCF Services (like our Service2 here in this demo) where
    // we have a section in our web.config to register services without the need of having a physical .svc file.
    // The section in the web.config looks like this:
    //
    //      <serviceHostingEnvironment multipleSiteBindingsEnabled="true" >
    //          <serviceActivations>
    //              <add relativeAddress="Service2.svc" service="WcfService2.Service2" />
    //          </serviceActivations>
    //      </serviceHostingEnvironment>
    //
    // And is this configuration what we are going to inject at runtime to simulate having that
    // information in our web.config, while we haven't.

    /// <summary>
    ///
    /// </summary>
    public class DynamicServiceHelper
    {
        private static readonly HashSet<string> staticActivations = new HashSet<string>();

        private static readonly Lazy<Hashtable> _serviceHostingEnvironmentServiceActivations = new Lazy<Hashtable>(() =>
        {
            // Make sure the hosting environment is initialized (this sets the HostingManager's properties)
            ServiceHostingEnvironment.EnsureInitialized();

            // Get the hosting Manager using reflection
            var hostingManagerField = typeof(ServiceHostingEnvironment).GetField("hostingManager", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField);
            var hostingManager = hostingManagerField.GetValue(null);

            var serviceActivationsField = hostingManager.GetType().GetField("serviceActivations", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField);
            var serviceActivations = serviceActivationsField.GetValue(hostingManager) as Hashtable;

            foreach (var key in serviceActivations.Keys)
            {
                staticActivations.Add((string)key);
            }

            return serviceActivations;
        }, true);

        private static Hashtable ServiceHostingEnvironmentServiceActivations { get { return _serviceHostingEnvironmentServiceActivations.Value; } }

        private static readonly Lazy<UriSchemeKeyedCollection> _baseAddresses = new Lazy<UriSchemeKeyedCollection>(() =>
        {
            // Get the base addresses
            var hostedTransportConfigurationManagerType = Type.GetType("System.ServiceModel.Activation.HostedTransportConfigurationManager, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
            var hostedTransportConfigurationManagerGetBaseAddressesMethod = hostedTransportConfigurationManagerType.GetMethod("GetBaseAddresses", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.InvokeMethod);
            var baseAddressesObj = hostedTransportConfigurationManagerGetBaseAddressesMethod.Invoke(null, new object[] { HostingEnvironment.ApplicationVirtualPath });

            return new UriSchemeKeyedCollection((Uri[])baseAddressesObj);
        }, true);

        public static UriSchemeKeyedCollection BaseAddresses { get { return _baseAddresses.Value; } }

        private readonly static object _lockObject = new object();

        public DynamicServiceHelper(ILogger logger)
        {
            this.Logger = logger;
        }

        public List<ReceivePortInfo> UpdateActivations(IDictionary<string, ReceivePortInfo> activeReceivePorts)
        {
            lock (_lockObject)
            {
                foreach (var key in ServiceHostingEnvironmentServiceActivations.Keys.Cast<string>().ToArray())
                {
                    if (!activeReceivePorts.ContainsKey(key) &&
                        !staticActivations.Contains(key))
                    {
                        this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Removing ServiceActivation for: {0}", key);
                        ServiceHostingEnvironmentServiceActivations.Remove(key);
                    }
                }

                var activatedReceivePorts = new List<ReceivePortInfo>();
                foreach (var kvp in activeReceivePorts)
                {
                    var address = kvp.Key;
                    if (!ServiceHostingEnvironmentServiceActivations.ContainsKey(address))
                    {
                        this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Adding ServiceActivation for: {0}", address);
                        var receivePort = kvp.Value;
                        var constructorString = string.Format("{0};{1}", receivePort.PublisherInfo.Id, receivePort.Id);
                        string value = string.Format("{0}|{1}|{2}", address, typeof(DynamicWebReceivePortServiceHostFactory).AssemblyQualifiedName, constructorString);
                        ServiceHostingEnvironmentServiceActivations.Add(address, value);
                        activatedReceivePorts.Add(receivePort);
                    }
                }

                return activatedReceivePorts;
            }
        }

        public void RegisterService(string addr, Type serviceFactory, string constructorString)
        {
            lock (_lockObject)
            {
                string value = string.Format("{0}|{1}|{2}", addr, serviceFactory.AssemblyQualifiedName, constructorString);
                if (!ServiceHostingEnvironmentServiceActivations.ContainsKey(addr))
                {
                    ServiceHostingEnvironmentServiceActivations.Add(addr, value);
                }
            }
        }

        public void UnRegisterService(string addr)
        {
            lock (_lockObject)
            {
                if (ServiceHostingEnvironmentServiceActivations.ContainsKey(addr))
                {
                    ServiceHostingEnvironmentServiceActivations.Remove(addr);
                }
            }
        }

        public ILogger Logger { get; set; }
    }
}